GoogleMapEditor.js ➔ ???   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
dl 0
loc 71
rs 9.1369
c 0
b 0
f 0
nop 2

8 Functions

Rating   Name   Duplication   Size   Complexity  
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onMarkerSave 0 3 1
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onMarkerPlaced 0 3 1
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onClickNavigationButton 0 3 1
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onMarkerDelete 0 3 1
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onLoad 0 3 1
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onCoordinatesChanged 0 3 1
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onMarkerClick 0 3 1
A GoogleMapEditor.js ➔ ... ➔ this._defaults.closures.onZoomChanged 0 3 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/** global: google */
2
3
import {createNavigationButton} from '../functions/createNavigationButton';
4
import {createAddnewContainer} from '../functions/createAddnewContainer';
5
import {createEditorContainer} from '../functions/createEditorContainer';
6
import {createRandomId} from '../functions/createRandomId';
7
import Marker from './Marker';
8
9
class GoogleMapEditor {
10
11
    constructor(container, options = {}) {
12
        this._instance = null;
13
        this._container = container;
14
        this._markers = [];
15
        this._options = {};
16
        this._navigationButton = null;
17
18
        this._defaults = {
19
            map: {
20
                center: {
21
                    lat: 55.1309504,
22
                    lng: 24.5499231
23
                },
24
                zoom: 7,
25
                scrollwheel: false,
26
                navigationControl: true,
27
                mapTypeControl: false,
28
                scaleControl: true,
29
                draggable: true,
30
                streetViewControl: false
31
            },
32
            sprite: 'assets/images/sprite.png',
33
            closures: {
34
                onLoad: function (instance) {
0 ignored issues
show
Unused Code introduced by
The parameter instance is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
35
36
                },
37
38
                onClickNavigationButton: function (instance) {
39
                    instance.onClickNavigationButton();
40
                },
41
42
                onMarkerClick: function (instance, marker) {
43
                    instance.onMarkerClick(marker);
44
                },
45
46
                onMarkerPlaced: function (instance, marker) {
0 ignored issues
show
Unused Code introduced by
The parameter instance is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter marker is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
47
48
                },
49
50
                onMarkerSave: function (instance, marker) {
0 ignored issues
show
Unused Code introduced by
The parameter marker is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter instance is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
51
52
                },
53
54
                onMarkerDelete: function (instance, marker) {
0 ignored issues
show
Unused Code introduced by
The parameter instance is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter marker is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
55
56
                },
57
58
                onZoomChanged: function (instance, zoomLevel) {
0 ignored issues
show
Unused Code introduced by
The parameter instance is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter zoomLevel is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
59
60
                },
61
62
                onCoordinatesChanged: function (instance, coordinates) {
0 ignored issues
show
Unused Code introduced by
The parameter coordinates is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter instance is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
63
64
                },
65
            },
66
            translations: {
67
                close: 'Close',
68
                address: 'Type your address',
69
                editMarker: 'Edit marker',
70
                displayInfo: 'Display info window?',
71
                content: 'Content',
72
                delete: 'Delete',
73
                save: 'Save'
74
            }
75
        };
76
77
        this.populateOptions(options);
78
        this.createInstance();
79
        this.populateMarkers();
80
        this.bindEvents();
81
    }
82
83
    getOptions() {
84
        return this._options;
85
    }
86
87
    bindEvents() {
88
        google.maps.event.addListenerOnce(this._instance, 'tilesloaded', () => {
89
            this._options.closures.onLoad(this);
90
91
            let sprite = this._options.sprite;
92
            let container = this._container;
93
94
            this._navigationButton = createNavigationButton(sprite, container);
95
96
            // initialize navigation button events
97
            this._navigationButton.addEventListener('click', () => {
98
                this._options.closures.onClickNavigationButton(this);
99
            });
100
        });
101
102
        google.maps.event.addListener(this._instance, 'zoom_changed', () => {
103
            this._options.closures.onZoomChanged(this, this._instance.getZoom());
104
        });
105
106
        google.maps.event.addListener(this._instance, 'dragend', () => {
107
            let center = this._instance.getCenter();
108
            this._options.closures.onCoordinatesChanged(this._instance, `${center.lat()},${center.lng()}`);
109
        });
110
111
        google.maps.event.addListener(this._instance, 'click', (event) => {
112
            this.placeMarker(event.latLng);
113
        });
114
    }
115
116
    onClickNavigationButton() {
117
        let container = createAddnewContainer(this._container, this._options.translations);
118
        let addressField = container.querySelector('input[name="address"]');
119
120
        addressField.focus();
121
122
        let searchBox = this.createSearchBox(addressField);
123
124
        searchBox.addListener('places_changed', () => {
125
            let places = searchBox.getPlaces();
126
127
            if (places.length == 0) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
128
129
            let bounds = new google.maps.LatLngBounds();
130
131
            places.forEach(place => {
132
                if (!place.geometry) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
133
134
                this.placeMarker(place.geometry.location, {address: place.formatted_address});
135
136
                if (place.geometry.viewport) {
137
                    bounds.union(place.geometry.viewport);
138
                } else {
139
                    bounds.extend(place.geometry.location);
140
                }
141
            });
142
143
            this._instance.fitBounds(bounds);
144
            container.parentNode.removeChild(container);
145
        });
146
    }
147
148
    collectFieldsData(container) {
149
        return {
150
            content: container.querySelector('textarea[name="content"]').value,
151
            displayWindow: container.querySelector('input[type="checkbox"]').checked
152
        };
153
    }
154
155
    onMarkerClick(marker) {
156
        if (!(marker instanceof Marker)) {
157
            return;
158
        }
159
160
        let container = createEditorContainer(this._container, this._options.translations, {
161
            address: marker.options.address,
162
            content: marker.options.content,
163
            displayWindow: marker.options.displayWindow
164
        });
165
166
        let searchBox = this.createSearchBox(container.querySelector('input[name="address"]'), container, false, marker);
167
        let bounds = null;
168
169
        searchBox.addListener('places_changed', () => {
170
            let places = searchBox.getPlaces();
171
172
            bounds = new google.maps.LatLngBounds();
173
174
            places.forEach(place => {
175
                if (!place.geometry) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
176
177
                marker.temporary = Object.assign(marker.temporary, {
178
                    location: place.geometry.location,
179
                    address: place.formatted_address
180
                });
181
182
                if (place.geometry.viewport) {
183
                    bounds.union(place.geometry.viewport);
184
                } else {
185
                    bounds.extend(place.geometry.location);
186
                }
187
            });
188
        });
189
190
        container.querySelector('button[name="save"]').addEventListener('click', () => {
191
            let data = this.collectFieldsData(container);
192
            // save all temporary object to options and fit bounds if location was changed
193
            marker.options = Object.assign(marker.options, marker.temporary, data);
194
195
            if ('location' in marker.temporary) {
196
                marker.instance.setPosition(marker.options.location);
197
                this._instance.fitBounds(bounds);
198
            }
199
200
            if ('content' in data) {
201
                marker.instance.infoWindow.setContent(data.content);
202
            }
203
204
            marker.temporary = {};
205
206
            this._options.closures.onMarkerSave(this, marker);
207
208
            container.parentNode.removeChild(container);
209
        });
210
211
        container.querySelector('button[name="delete"]').addEventListener('click', () => {
212
            this._options.closures.onMarkerDelete(this, marker);
213
            this.removeMarker(marker);
214
            container.parentNode.removeChild(container);
215
        });
216
    }
217
218
    createSearchBox(inputField) {
219
        return new google.maps.places.SearchBox(inputField);
220
    }
221
222
    populateOptions(options = {}) {
223
        if ('map' in options) {
224
            options.map = Object.assign(this._defaults.map, options.map);
225
        }
226
227
        if ('closures' in options) {
228
            options.closures = Object.assign(this._defaults.closures, options.closures);
229
        }
230
231
        this._options = Object.assign(this._defaults, options);
232
233
        let data = this.getJSONOrEmpty(this._container.getAttribute('data-options'));
234
235
        if ('map' in data) {
236
            this._options.map = Object.assign(this._options.map, data.map);
237
            delete data['map'];
238
        }
239
240
        if ('closures' in data) {
241
            this._options.closures = Object.assign(this._options.closures, data.closures);
242
            delete data['closures'];
243
        }
244
245
        this._options = Object.assign(this._options, data);
246
    }
247
248
    populateMarkers() {
249
        let markers = this.getJSONOrEmpty(this._container.getAttribute('data-markers'));
250
251
        if(markers) {
252
            markers.forEach(marker => {
253
                let coordinates = marker.coordinates.split(',').map(Number).filter(x => !isNaN(x));
254
255
                this.placeMarker(new google.maps.LatLng(coordinates[0], coordinates[1]), {
256
                    address: marker.address || '',
257
                    content: marker.content || '',
258
                    displayWindow: marker.displayWindow,
259
                    instanceId: marker.instanceId
260
                }, false);
261
            });
262
        }
263
    }
264
265
    createInstance() {
266
        this._instance = new google.maps.Map(this._container, this._options.map);
267
    }
268
269
    placeMarker(location, settings = {}, useClosure = true) {
270
        let icons = {
271
            default: new google.maps.MarkerImage(
272
                this._options.sprite,
273
                new google.maps.Size(26, 32),
274
                new google.maps.Point(0, 32)
275
            ),
276
            active: new google.maps.MarkerImage(
277
                this._options.sprite,
278
                new google.maps.Size(26, 32),
279
                new google.maps.Point(32, 32)
280
            )
281
        };
282
283
        let marker = new Marker(Object.assign({
284
            instanceId: createRandomId(),
285
            markerImage: icons.default,
286
            location: location,
287
            draggable: true,
288
            infoWindowOn: 'hover',
289
            events: {
290
                onMouseover: (currentMarker) => {
291
                    currentMarker.instance.setIcon(icons.active);
292
                },
293
294
                onMouseout: (currentMarker) => {
295
                    currentMarker.instance.setIcon(icons.default);
296
                }
297
            }
298
        }, settings), this._instance);
299
300
        google.maps.event.addListener(marker.instance, 'click', () => {
301
            this._options.closures.onMarkerClick(this, marker);
302
        });
303
304
        google.maps.event.addListener(marker.instance, 'dragend', () => {
305
            this._options.closures.onMarkerSave(this, marker);
306
        });
307
308
        if (useClosure) {
309
            this._options.closures.onMarkerPlaced(this, marker);
310
        }
311
312
        this._markers.push(marker);
313
    }
314
315
    removeMarker(marker) {
316
        marker.instance.setMap(null);
317
        let key = false;
318
319
        this._markers.forEach((item, index) => {
320
            if (marker.instanceId == item.instanceId) {
321
                key = index;
322
            }
323
        });
324
325
        if (!isNaN(key)) {
326
            delete this._markers[key];
327
        }
328
    }
329
330
    getMarkers() {
331
        return this._markers;
332
    }
333
334
    getJSONOrEmpty(input) {
335
        if (typeof input == 'undefined' || input == '') {
336
            return {};
337
        }
338
339
        return JSON.parse(input);
340
    }
341
}
342
343
export default GoogleMapEditor;